@namespace Oqtane.Modules.Controls
@inherits ModuleControlBase
@inject IStringLocalizerFactory LocalizerFactory
@inject IStringLocalizer<SharedResources> SharedLocalizer
@typeparam TableItem

@if (ItemList != null)
{
    @if (!string.IsNullOrEmpty(SearchProperties))
    {
        <div class="input-group my-3">
            <input id="search" class="form-control" placeholder=@string.Format(Localizer["SearchPlaceholder"], FormatSearchProperties()) @bind="@_search" />
            <button type="button" class="btn btn-primary" @onclick="Search">@SharedLocalizer["Search"]</button>
            <button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Reset"]</button>
        </div>
    }

    @if ((Toolbar == "Top" || Toolbar == "Both") && _pages > 0 && Items.Count() > _maxItems)
    {
        <ul class="pagination justify-content-center my-2">
            <li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
                <a class="page-link" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
            </li>
            @if (_pages > _displayPages && _displayPages > 1)
            {
                <li class="page-item@((_page > _displayPages) ? " app-pager-pointer" : " disabled")">
                    <a class="page-link" @onclick=@(async () => SkipPages("back"))><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
                </li>
            }
            <li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
                <a class="page-link" @onclick=@(async () => NavigateToPage("previous"))><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></a>
            </li>
            @for (int i = _startPage; i <= _endPage; i++)
            {
                var pager = i;
                if (pager == _page)
                {
                    <li class="page-item app-pager-pointer active">
                        <a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
                    </li>
                }
                else
                {
                    <li class="page-item app-pager-pointer">
                        <a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
                    </li>
                }
            }
            <li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
                <a class="page-link" @onclick=@(async () => NavigateToPage("next"))><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a>
            </li>
            @if (_pages > _displayPages && _displayPages > 1)
            {
                <li class="page-item@((_endPage < _pages) ? " app-pager-pointer" : " disabled")">
                    <a class="page-link" @onclick=@(async () => SkipPages("forward"))><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
                </li>
            }
            <li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
                <a class="page-link" @onclick=@(async () => UpdateList(_pages))><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
            </li>
            <li class="page-item disabled">
                <a class="page-link" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
            </li>
        </ul>
    }
    @if (Format == "Table" && Row != null)
    {
        <div class="table-responsive">
            <table class="@Class">
                <thead>
                    <tr class="@RowClass">@Header</tr>
                </thead>
                <tbody>
                    @foreach (var item in ItemList)
                    {
                        <tr class="@RowClass">@Row(item)</tr>
                        @if (Detail != null)
                        {
                            <tr>@Detail(item)</tr>
                        }
                    }
                </tbody>
                <tfoot>
                    <tr class="@RowClass">@Footer</tr>
                </tfoot>
            </table>
        </div>
    }
    @if (Format == "Grid" && Row != null)
    {
        int count = 0;
        int rows = 0;
        int cols = 0;
        if (ItemList != null)
        {
            if (_columns == 0)
            {
                count = ItemList.Count();
                rows = 1;
                cols = count;
            }
            else
            {
                count = (int)Math.Ceiling(ItemList.Count() / (decimal)_columns) * _columns;
                rows = count / _columns;
                cols = _columns;
            }
        }
        <div class="@Class">
            @for (int row = 0; row < rows; row++)
            {
                <div class="@RowClass">
                    @for (int col = 0; col < cols; col++)
                    {
                        int index = (row * _columns) + col;
                        if (index < ItemList.Count())
                        {
                            <div class="@ColumnClass">@Row(ItemList.ElementAt(index))</div>
                        }
                        else
                        {
                            <div>&nbsp;</div>
                        }
                    }
                </div>
            }
        </div>
    }
    @if ((Toolbar == "Bottom" || Toolbar == "Both") && _pages > 0 && Items.Count() > _maxItems)
    {
        <ul class="pagination justify-content-center my-2">
            <li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
                <a class="page-link" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
            </li>
            @if (_pages > _displayPages && _displayPages > 1)
            {
                <li class="page-item@((_page > _displayPages) ? " app-pager-pointer" : " disabled")">
                    <a class="page-link" @onclick=@(async () => SkipPages("back"))><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
                </li>
            }
            <li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
                <a class="page-link" @onclick=@(async () => NavigateToPage("previous"))><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></a>
            </li>
            @for (int i = _startPage; i <= _endPage; i++)
            {
                var pager = i;
                if (pager == _page)
                {
                    <li class="page-item app-pager-pointer active">
                        <a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
                    </li>
                }
                else
                {
                    <li class="page-item app-pager-pointer">
                        <a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
                    </li>
                }
            }
            <li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
                <a class="page-link" @onclick=@(async () => NavigateToPage("next"))><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a>
            </li>
            @if (_pages > _displayPages && _displayPages > 1)
            {
                <li class="page-item@((_endPage < _pages) ? " app-pager-pointer" : " disabled")">
                    <a class="page-link" @onclick=@(async () => SkipPages("forward"))><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
                </li>
            }
            <li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
                <a class="page-link" @onclick=@(async () => UpdateList(_pages))><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
            </li>
            <li class="page-item disabled">
                <a class="page-link" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
            </li>
        </ul>
    }
}

@code {
    private IStringLocalizer Localizer;
    private int _pages = 0;
    private int _page = 1;
    private int _maxItems = 10;
    private int _displayPages = 5;
    private int _startPage = 0;
    private int _endPage = 0;
    private int _columns = 0;
    private string _search = "";

    private IEnumerable<TableItem> AllItems;

    [Parameter]
    public string Format { get; set; } // Table or Grid

    [Parameter]
    public string Toolbar { get; set; } // Top, Bottom or Both

    [Parameter]
    public RenderFragment Header { get; set; } = null; // only applicable to Table layouts

    [Parameter]
    public RenderFragment<TableItem> Row { get; set; } = null; // required

    [Parameter]
    public RenderFragment Footer { get; set; } = null; // only applicable to Table layouts

    [Parameter]
    public RenderFragment<TableItem> Detail { get; set; } = null; // only applicable to Table layouts

    [Parameter]
    public IEnumerable<TableItem> Items { get; set; } // the IEnumerable data source

    [Parameter]
    public string PageSize { get; set; } // number of items to display on a page

    [Parameter]
    public string Columns { get; set; } // only applicable to Grid layouts - default is zero indicating use responsive behavior

    [Parameter]
    public string CurrentPage { get; set; } // sets the initial page to display

    [Parameter]
    public string DisplayPages { get; set; } // maximum number of page numbers to display for user selection

    [Parameter]
    public string Class { get; set; } // class for the containing element - ie. <table> for Table or <div> for Grid

    [Parameter]
    public string RowClass { get; set; } // class for row element - ie. <tr> for Table or <div> for Grid

    [Parameter]
    public string ColumnClass { get; set; } // class for column element - only applicable to Grid format

    [Parameter]
    public Action<int> OnPageChange { get; set; } // a method to be executed in the calling component when the page changes

    [Parameter]
    public string SearchProperties { get; set; } // comma delimited list of property names to include in search

    private IEnumerable<TableItem> ItemList { get; set; }

    protected override void OnInitialized()
    {
        Localizer = LocalizerFactory.Create(GetType().FullName);
    }

    protected override void OnParametersSet()
    {
        if (string.IsNullOrEmpty(Format))
        {
            Format = "Table";
        }

        if (string.IsNullOrEmpty(Toolbar))
        {
            Toolbar = "Top";
        }

        if (string.IsNullOrEmpty(Class))
        {
            if (Format == "Table")
            {
                Class = "table table-borderless";
            }
            else
            {
                Class = "container-fluid";
            }
        }

        if (string.IsNullOrEmpty(RowClass))
        {
            if (Format == "Table")
            {
                RowClass = "";
            }
            else
            {
                RowClass = "row";
            }
        }

        if (string.IsNullOrEmpty(ColumnClass))
        {
            if (Format == "Table")
            {
                ColumnClass = "";
            }
            else
            {
                ColumnClass = "col";
            }
        }

        if (!string.IsNullOrEmpty(SearchProperties))
        {
            AllItems = Items; // only used in search
            if (!string.IsNullOrEmpty(_search))
            {
                Search();
            }
        }

        if (!string.IsNullOrEmpty(PageSize))
        {
            _maxItems = int.Parse(PageSize);
        }

        if (!string.IsNullOrEmpty(Columns))
        {
            _columns = int.Parse(Columns);
        }

        if (!string.IsNullOrEmpty(DisplayPages))
        {
            _displayPages = int.Parse(DisplayPages);
        }

        if (!string.IsNullOrEmpty(CurrentPage))
        {
            _page = int.Parse(CurrentPage);
        }
        else
        {
            _page = 1;
        }
        if (_page < 1) _page = 1;

        _startPage = 0;
        _endPage = 0;

        if (Items != null)
        {
            _pages = (int)Math.Ceiling(Items.Count() / (decimal)_maxItems);
            if (_page > _pages)
            {
                _page = _pages;
            }
            SetPagerSize();
        }
    }

    public void SetPagerSize()
    {
        _startPage = ((_page - 1) / _displayPages) * _displayPages + 1;
        _endPage = _startPage + _displayPages - 1;
        if (_endPage > _pages)
        {
            _endPage = _pages;
        }
        ItemList = Items.Skip((_page - 1) * _maxItems).Take(_maxItems);
        StateHasChanged();
        OnPageChange?.Invoke(_page);
    }

    public void UpdateList(int page)
    {
        _page = page;
        SetPagerSize();
    }

    public void SkipPages(string direction)
    {
        switch (direction)
        {
            case "forward":
                _page = _endPage + 1;
                break;
            case "back":
                _page = _startPage - 1;
                break;
        }

        SetPagerSize();
    }

    public void NavigateToPage(string direction)
    {
        switch (direction)
        {
            case "next":
                if (_page < _pages)
                {
                    _page += 1;
                }
                break;
            case "previous":
                if (_page > 1)
                {
                    _page -= 1;
                }
                break;
        }

        UpdateList(_page);
    }

    public void Search()
    {
        if (!string.IsNullOrEmpty(_search))
        {
            Items = AllItems.Where(item =>
            {
                var values = SearchProperties.Split(',')
                    .Select(itemType => GetPropertyValue(item, itemType))
                    .Where(value => value != null)
                    .Select(value => value.ToString().ToLower());

                return values.Any(value => value.Contains(_search.ToLower()));
            }).ToList();
        }
        else
        {
            Items = AllItems;
        }
        _pages = (int)Math.Ceiling(Items.Count() / (decimal)_maxItems);
        UpdateList(1);
    }

    private object GetPropertyValue(object obj, string propertyName)
    {
        var index = propertyName.IndexOf(".");
        if (index != -1)
        {
            var propertyInfo = obj.GetType().GetProperty(propertyName.Substring(0, index));
            if (propertyInfo != null)
            {
                return GetPropertyValue(propertyInfo.GetValue(obj), propertyName.Substring(index + 1));
            }
            return null;
        }
        else
        {
            var propertyInfo = obj.GetType().GetProperty(propertyName);
            if (propertyInfo != null)
            {
                return propertyInfo.GetValue(obj);
            }
            return null;
        }
    }

    public void Reset()
    {
        _search = "";
        Items = AllItems;
        _pages = (int)Math.Ceiling(Items.Count() / (decimal)_maxItems);
        UpdateList(1);
    }

    private string FormatSearchProperties()
    {
        var properties = new List<string>();
        foreach (var property in SearchProperties.Split(',', StringSplitOptions.RemoveEmptyEntries))
        {
            var index = property.LastIndexOf(".");
            if (index != -1)
            {
                properties.Add(property.Substring(index + 1));
            }
            else
            {
                properties.Add(property);
            }
        }
        return string.Join(",", properties);
    }
}
